/*
 * app_bl0937.c
 *
 *  Created on: 2023年9月18日
 *      Author: zlw
 */
#include "app_bl0937.h"
#if defined(SM_SMART_PLUG)
#include "em_cmu.h"
#include "em_emu.h"
#include "em_chip.h"
#include "em_gpio.h"
#include "em_timer.h"
#include "stddef.h"
#include "softwareMultiTimer.h"
#include "app_log.h"
#include "device.h"
#include "stdlib.h"
#include "app_api.h"
#include "app_key_value.h"

#define CALI_CNT_MAX (8)
#define CALI_CNT     (4)

static selState_t selStatae = SEL_CURRENT;

static bl0937Cnt_t bl0937Cnt = {0x00};

static energyFre_t energyFre = {0x00};
static energyFre_t energyLastV = {0x00};
static uint32_t energyCnt = 0;

static calibration_t calibration = {0};
static measure_t measureState = MEASURE_NONE;

static uint8_t currentFreIndex = 0;
static uint8_t voltageFreIndex = 0;
static uint8_t powerFreIndex = 0;

static uint16_t caliCurrentFre[CALI_CNT_MAX] = {0x00};
static uint16_t caliVoltageFre[CALI_CNT_MAX] = {0x00};
static uint16_t caliPowerFre[CALI_CNT_MAX] = {0x00};

static overCnt_t overCnt = {0x00};

static uint16_t currentGet_t(void);
static uint16_t voltageGet_t(void);
static uint16_t powerGet_t(void);

static bool currentState = false;
static bool voltageState = false;
static bool powerState = false;

static timeCnt_t currentTimeCnt = {0x00};
static timeCnt_t voltageTimeCnt = {0x00};
static timeCnt_t powerTimeCnt = {0x00};

static bool readEnergyState = false;

slideWindow_t currentWindow = {0x00};
slideWindow_t powerWindow = {0x00};

void resetCalibration(void)
{
  calibration.currentCalibration = 0;
  calibration.powerCalibration = 0;
  calibration.voltageCalibration = 0;
  measureState = MEASURE_NONE;
  setThisParam(smartPlugCalibration, &calibration);
}

static uint16_t data_buffer_avg(uint16_t * data, uint8_t length)
{
  uint8_t i = 0;
  uint32_t sum = 0;

  for(i=0;i<length;i++)
    sum += data[i];

  return (sum/length);
}

int int_cmp(const void* p1, const void* p2)
{
    return (int)(*(uint16_t*)p1 - *(uint16_t*)p2);//升序排序
}

static void slideWindowDataPush(slideWindow_t *slideWindow, uint16_t data)
{
  slideWindow->data[slideWindow->index] = data;
  if(slideWindow->index<SLIDE_WINDOW_SIZE-1) {
      slideWindow->index ++;
  }
  else {
      slideWindow->index = 0;
  }
  if(slideWindow->dataCnt < SLIDE_WINDOW_SIZE) {
      slideWindow->dataCnt++;
  }
}

static uint16_t getSlideWindowAvg(slideWindow_t *slideWindow)
{
  uint16_t avgValue = 0;
  uint16_t buffer[SLIDE_WINDOW_SIZE] = {0x00};

  memcpy(buffer, slideWindow->data, sizeof(slideWindow->data));
  qsort(buffer, SLIDE_WINDOW_SIZE, sizeof(buffer[0]), int_cmp);

  if(slideWindow->dataCnt<4) {
      avgValue = data_buffer_avg(slideWindow->data, slideWindow->dataCnt);
  }
  else {
      avgValue = data_buffer_avg(&buffer[SLIDE_WINDOW_SIZE-slideWindow->dataCnt], slideWindow->dataCnt-2);
  }
  return avgValue;
}


static uint8_t currentCalibrationGet(void)
{
  uint8_t currCalibration = 0;
  uint16_t currFre = 0;

  if(currentFreIndex == CALI_CNT_MAX) {
      currFre = data_buffer_avg(caliCurrentFre+CALI_CNT, CALI_CNT_MAX-CALI_CNT);
  }
  if(currFre > 0)
    currCalibration = (uint8_t)(CALIBRATION_CURRENT / currFre);

  return currCalibration;
  //return 120;
}

static uint8_t voltageCalibrationGet(void)
{
  uint8_t volCalibration = 0;
  uint16_t volFre = 0;

  if(voltageFreIndex == CALI_CNT_MAX)
    volFre = data_buffer_avg(caliVoltageFre+CALI_CNT, CALI_CNT_MAX-CALI_CNT);
  if(volFre > 0)
    volCalibration = (uint8_t)(CALIBRATION_VOLTAGE / volFre);

  return volCalibration;
  //return 130;
}

static uint8_t powerCalibrationGet(void)
{
  uint8_t powCalibration = 0;
  uint16_t powFre = 0;

  if(powerFreIndex == CALI_CNT_MAX)
    powFre = data_buffer_avg(caliPowerFre+CALI_CNT, CALI_CNT_MAX-CALI_CNT);
  if(powFre > 0)
    powCalibration = (uint8_t)(CALIBRATION_POWER / powFre);

  return powCalibration;
  //return 103;
}

/**************************************************************************//**
 * @brief
 *    GPIO initialization
 *****************************************************************************/
static void initGPIO(void)
{
  // Configure PB0 pin as input with the pull-up and filter enabled.
  GPIO_PinModeSet(BL0937_CF_PORT, BL0937_CF_PIN,
                  gpioModeInputPullFilter, 0);
  // Configure PB0 pin as input with the pull-up and filter enabled.
  GPIO_PinModeSet(BL0937_CF1_PORT, BL0937_CF1_PIN,
                  gpioModeInputPullFilter, 0);
  // Route the PB0 pin to TIMER0 capture/compare channel 0 and enable
  GPIO->TIMERROUTE[0].ROUTEEN  = GPIO_TIMER_ROUTEEN_CC0PEN;
  GPIO->TIMERROUTE[0].CC0ROUTE = (BL0937_CF_PORT << _GPIO_TIMER_CC0ROUTE_PORT_SHIFT)
                               | (BL0937_CF_PIN << _GPIO_TIMER_CC0ROUTE_PIN_SHIFT);
  GPIO->TIMERROUTE[1].ROUTEEN  = GPIO_TIMER_ROUTEEN_CC0PEN;
  GPIO->TIMERROUTE[1].CC0ROUTE = (BL0937_CF1_PORT << _GPIO_TIMER_CC0ROUTE_PORT_SHIFT)
                               | (BL0937_CF1_PIN << _GPIO_TIMER_CC0ROUTE_PIN_SHIFT);
}

/**************************************************************************//**
 * @brief
 *    TIMER initialization
 *
 * @note
 *    Prescaling the TIMER clock may or may not be necessary in a given
 *    application.  Here, prescaling the clock by 1024 (each TIMER tick
 *    is 1024 HFPERLKs) makes it easier to see the difference between
 *    successive capture values.
 *****************************************************************************/
static void initTIMER0(void)
{
  CMU_ClockSelectSet(cmuClock_TIMER0, cmuSelect_HFXO);
  CMU_ClockSelectSet(cmuClock_TIMER1, cmuSelect_HFXO);
  CMU_ClockEnable(cmuClock_TIMER0, true);
  CMU_ClockEnable(cmuClock_TIMER1, true);
  // Initialize TIMER0
  TIMER_Init_TypeDef timerInit = TIMER_INIT_DEFAULT;
  timerInit.prescale = timerPrescale32;
  timerInit.enable = false;               // Don't start the timer yet
  timerInit.riseAction = timerInputActionReloadStart;
  timerInit.fallAction = timerInputActionStop;

  TIMER_Init(TIMER0, &timerInit);
  TIMER_Init(TIMER1, &timerInit);

  // Configure TIMER0 capture/compare channel 0 for input capture
  TIMER_InitCC_TypeDef timerCCInit = TIMER_INITCC_DEFAULT;
  timerCCInit.eventCtrl = timerEventFalling;
  timerCCInit.edge = timerEdgeBoth;
  timerCCInit.mode = timerCCModeCapture;
  timerCCInit.filter = true;

  // Enable interrupts on capture/compare channel 0
  TIMER_InitCC(TIMER0, 0, &timerCCInit);
  TIMER_InitCC(TIMER1, 0, &timerCCInit);

  TIMER_IntEnable(TIMER0, TIMER_IEN_CC0);
  TIMER_IntEnable(TIMER1, TIMER_IEN_CC0);

  NVIC_EnableIRQ(TIMER0_IRQn);
  NVIC_EnableIRQ(TIMER1_IRQn);

  // Now start the TIMER
  TIMER_Enable(TIMER0, true);
  TIMER_Enable(TIMER1, true);
}

/**************************************************************************//**
 * @brief
 *    TIMER 0 handler
 *****************************************************************************/
void TIMER0_IRQHandler(void)
{
  uint32_t powerWide = 0;
  // Acknowledge the interrupt
  uint32_t flags = TIMER_IntGet(TIMER0);
  TIMER_IntClear(TIMER0, flags);
  // Check for capture event on channel 0
  if (flags & TIMER_IF_CC0) {
      powerWide = TIMER_CaptureGet(TIMER0, 0);
      if(powerWide >= 35 && powerWide < 60) {
          if(powerState) {
              powerState = false;
              powerTimeCnt.startCnt = READ_SYS_RTC_COUNT();
          } else {
              powerTimeCnt.endCnt = READ_SYS_RTC_COUNT();
          }
          bl0937Cnt.powerCnt ++;
      }
  }
}

void TIMER1_IRQHandler(void)
{
  uint32_t curVolWide = 0;
  // Acknowledge the interrupt
  uint32_t flags = TIMER_IntGet(TIMER1);
  TIMER_IntClear(TIMER1, flags);
  // Check for capture event on channel 0
  if (flags & TIMER_IF_CC0) {
      curVolWide = TIMER_CaptureGet(TIMER1, 0);
      if(curVolWide >= 35 && curVolWide < 60) {
          if(selStatae == SEL_CURRENT) {
              if(currentState) {
                  currentState = false;
                  currentTimeCnt.startCnt = READ_SYS_RTC_COUNT();
              } else {
                  currentTimeCnt.endCnt = READ_SYS_RTC_COUNT();
              }
              bl0937Cnt.volCurrCnt.currentCnt ++;
          }
          else if(selStatae == SEL_VOLTAGE) {
              if(voltageState) {
                  voltageState = false;
                  voltageTimeCnt.startCnt = READ_SYS_RTC_COUNT();
              } else {
                  voltageTimeCnt.endCnt = READ_SYS_RTC_COUNT();
              }
              bl0937Cnt.volCurrCnt.voltageCnt ++;
          }
      }
  }
}

static ds_gpio_t sel = {
    .port = BL0937_SEL_PORT,
    .pin = BL0937_SEL_PIN,
    .mode = gpioModePushPull,
    .initial = 0,
    .valid = 1,
};

static void selGpioSelect(selState_t state)
{
  powerState = true;
  if(state == SEL_CURRENT) {
      currentState = true;
      selStatae = SEL_CURRENT;
      ds_gpio_off(&sel);
  }
  else if(state == SEL_VOLTAGE) {
      voltageState = true;
      selStatae = SEL_VOLTAGE;
      ds_gpio_on(&sel);
  }
}

static MultiTimer bl0937Timer[3] = {
    {.Handle = 0},
    {.Handle = 1},
    {.Handle = 2},
};

static void bl0937TimeroutHandlerCallback(MultiTimer* timer, void *userData)
{
  (void)userData;

  if(timer->Handle == 0) {
      readEnergyState = true;
      softwareMultiTimerStart(EN_LP_HARD_TIMER_EM3_SYS_RTC_1, timer, MEASURE_INTERVAL,  bl0937TimeroutHandlerCallback,NULL,1);
  }
  else if(timer->Handle == 1) {
      if(measureState == MEASURE_CALBRATION) {
          calibration.currentCalibration = currentCalibrationGet();
          calibration.voltageCalibration = voltageCalibrationGet();
          calibration.powerCalibration = powerCalibrationGet();
          if(calibration.currentCalibration || calibration.voltageCalibration || calibration.powerCalibration) {
              setThisParam(smartPlugCalibration, &calibration);
          }
          if(calibration.currentCalibration == 0
              || calibration.voltageCalibration == 0
              || calibration.powerCalibration == 0) {
              softwareMultiTimerStart(EN_LP_HARD_TIMER_EM3_SYS_RTC_1, &bl0937Timer[1], 32768,  bl0937TimeroutHandlerCallback,NULL,1);
          }
          else {
              measureState = MEASURE_NORMAL;
              calibration_finist_callback();
          }
      }
  }
  else if(timer->Handle == 2) {
      uint32_t summation = 0;
      if(getThisParam(smartPlugEnergy, &summation) != 0) {
          summation = 0;
      }
      summation += (energyCnt * calibration.powerCalibration / 360000);
      DS_app_log_info("summation = %ld \r\n", summation);
      if(summation > 0) {
          setThisParam(smartPlugEnergy, &summation);
          energyCnt = 0;
      }
      softwareMultiTimerStart(EN_LP_HARD_TIMER_EM3_SYS_RTC_1, &bl0937Timer[2], ENERGY_SAVE_INTERVAL,  bl0937TimeroutHandlerCallback,NULL,1);
  }
}

static void bl0937_measure_start(void)
{
  if(0 != getThisParam(smartPlugCalibration, &calibration)
      || calibration.currentCalibration == 0
      || calibration.voltageCalibration == 0
      || calibration.powerCalibration == 0) {
      measureState = MEASURE_CALBRATION;
      softwareMultiTimerStart(EN_LP_HARD_TIMER_EM3_SYS_RTC_1, &bl0937Timer[1], 4*32768,  bl0937TimeroutHandlerCallback,NULL,1);
  }
  else if(0 == getThisParam(smartPlugCalibration, &calibration)) {
      measureState = MEASURE_NORMAL;
  }
}

bool getReadEnergyState(void)
{
  return readEnergyState;
}

static energyStateCallback stateCallback[energyStateNum] = {NULL};

void readEnergyHandler(void)
{
  readEnergyState = false;

  if(selStatae == SEL_CURRENT) {
      if(bl0937Cnt.volCurrCnt.currentCnt>1 && currentTimeCnt.endCnt!=currentTimeCnt.startCnt) {
          energyFre.current =  32768 * (bl0937Cnt.volCurrCnt.currentCnt-1) * 10 / (currentTimeCnt.endCnt-currentTimeCnt.startCnt);
      } else {
          energyFre.current = 0;
      }
      slideWindowDataPush(&currentWindow, currentGet_t());

#if 0
      keyValue_t plugData = {0x00};
      plugData.id = 0xE0;
      plugData.valueSize = 9;
      plugData.value[0] = 8;
      plugData.value[1] = (bl0937Cnt.volCurrCnt.currentCnt >> 8)&0xff;
      plugData.value[2] = bl0937Cnt.volCurrCnt.currentCnt&0xff;
      plugData.value[3] = (timeCntTemp >> 24)&0xff;
      plugData.value[4] = (timeCntTemp >> 16)&0xff;
      plugData.value[5] = (timeCntTemp >> 8)&0xff;
      plugData.value[6] = timeCntTemp&0xff;
      plugData.value[7] = (energyFre.current >> 8)&0xff;
      plugData.value[8] = energyFre.current&0xff;
      set_key_value(&plugData);
#endif
      bl0937Cnt.volCurrCnt.currentCnt = 0;
      selGpioSelect(SEL_VOLTAGE);
  }
  else if(selStatae == SEL_VOLTAGE) {
      if(bl0937Cnt.volCurrCnt.voltageCnt>1 && voltageTimeCnt.endCnt!=voltageTimeCnt.startCnt) {
          energyFre.voltage = 32768 * (bl0937Cnt.volCurrCnt.voltageCnt-1) * 10 / (voltageTimeCnt.endCnt-voltageTimeCnt.startCnt);
      } else {
          energyFre.voltage = 0;
      }
      bl0937Cnt.volCurrCnt.voltageCnt = 0;
      selGpioSelect(SEL_CURRENT);
  }
  if(bl0937Cnt.powerCnt>1 && powerTimeCnt.endCnt!=powerTimeCnt.startCnt) {
      energyFre.power = 32768 * (bl0937Cnt.powerCnt-1) * 10 / (powerTimeCnt.endCnt-powerTimeCnt.startCnt);
  } else {
      energyFre.power = 0;
  }
  slideWindowDataPush(&powerWindow, powerGet_t());

  if(measureState == MEASURE_NORMAL) {
      energyCnt += bl0937Cnt.powerCnt;
  }
  bl0937Cnt.powerCnt = 0;

  if(measureState == MEASURE_CALBRATION) {
      if(energyFre.power > POWER_CALI_MIN && energyFre.power < POWER_CALI_MAX) {
          if(abs((int)(energyFre.power-energyLastV.power)) < 30 && powerFreIndex < CALI_CNT_MAX) {
              caliPowerFre[powerFreIndex ++] = energyFre.power;
          }
          energyLastV.power = energyFre.power;
          //当有功率值时才开始采集电压电流校准
          if(energyFre.current > CURRNET_CALI_MIN && energyFre.current < CURRNET_CALI_MAX) {
              if(abs((int)(energyFre.current-energyLastV.current)) < 30 && currentFreIndex < CALI_CNT_MAX) {
                  caliCurrentFre[currentFreIndex ++] = energyFre.current;
              }
              energyLastV.current = energyFre.current;
          }
          if(energyFre.voltage > VOLTAGE_CALI_MIN && energyFre.voltage < VOLTAGE_CALI_MAX) {
              if(abs((int)(energyFre.voltage-energyLastV.voltage)) < 50 && voltageFreIndex < CALI_CNT_MAX) {
                  caliVoltageFre[voltageFreIndex ++] = energyFre.voltage;
              }
              energyLastV.voltage = energyFre.voltage;
          }
      }
  }
  else {
      if(currentGet_t() > CURRENT_OVERLOAD) {
          overCnt.iOverCnt ++;
          if(overCnt.iOverCnt >= ENERGY_OVERLOAD_CNT) {
              overCnt.iOverCnt = 0;
              stateCallback[currentOverload](currentGet_t());
          }
      }
      else {
          overCnt.iOverCnt = 0;
          stateCallback[currentNormal](currentGet_t());
      }
      if(voltageGet_t() > VOLTAGE_OVERLOAD) {
          overCnt.vNormal = 0;
          overCnt.vUnderCnt = 0;
          overCnt.vOverCnt++;
          if(overCnt.vOverCnt >= ENERGY_OVERLOAD_CNT) {
              overCnt.vOverCnt = 0;
              stateCallback[voltageOverload](voltageGet_t());
          }
      }
      else if(voltageGet_t() < VOLTAGE_UNDER) {
          overCnt.vNormal = 0;
          overCnt.vOverCnt = 0;
          overCnt.vUnderCnt ++;
          if(overCnt.vUnderCnt >= ENERGY_OVERLOAD_CNT) {
              overCnt.vUnderCnt = 0;
              stateCallback[voltageUnderload](voltageGet_t());
          }
      }
      else if(voltageGet_t() >= VOLTAGE_UNDER_RECOVER && voltageGet_t() <= VOLTAGE_OVERLOAD_RECOVER){
          overCnt.vNormal ++;
          if(overCnt.vNormal >= ENERGY_OVERLOAD_CNT) {
              overCnt.vOverCnt = 0;
              overCnt.vUnderCnt = 0;
              overCnt.vNormal = 0;
              stateCallback[voltageNomal](voltageGet_t());
          }
      }
      if(powerGet_t() > POWER_OVERLOAD) {
          overCnt.pOverCnt++;
          if(overCnt.pOverCnt >= ENERGY_OVERLOAD_CNT) {
              overCnt.pOverCnt = 0;
              stateCallback[powerOverload](powerGet_t());
          }
      }
      else {
          overCnt.pOverCnt = 0;
          stateCallback[powerNormal](powerGet_t());
      }
    }
}

void energyStateCallbackRegister(energyState_t state, energyStateCallback callback)
{
  stateCallback[state] = callback;
}

/**
 * @brief: timer capture init
 * select gpio init
 * */
void bl0937_init(void)
{
  initGPIO();
  initTIMER0();
  ds_gpio_init(&sel);
  selGpioSelect(SEL_CURRENT);
  softwareMultiTimerStart(EN_LP_HARD_TIMER_EM3_SYS_RTC_1, &bl0937Timer[0], MEASURE_INTERVAL,  bl0937TimeroutHandlerCallback,NULL,1);
  softwareMultiTimerStart(EN_LP_HARD_TIMER_EM3_SYS_RTC_1, &bl0937Timer[2], ENERGY_SAVE_INTERVAL,  bl0937TimeroutHandlerCallback,NULL,1);
  bl0937_measure_start();
}

/**
 * @brief: get mersure state
 * return: measure state
 * */
measure_t measure_state_get(void)
{
  return measureState;
}

/**
 * @brief: get current
 * return :current unit:mA
 * */
static uint16_t currentGet_t(void)
{
  uint16_t current = 0;
  current = energyFre.current * calibration.currentCalibration / CURRENT_MULTI;
  return current;
}
uint16_t currentGet(void)
{
  uint16_t current = 0;
  current = getSlideWindowAvg(&currentWindow);
  return current;
}

/**
 * @brief: get voltage
 * return :voltage - 90  unit:V
 * */
static uint16_t voltageGet_t(void)
{
  uint16_t voltage = 0;
  voltage = energyFre.voltage * calibration.voltageCalibration / VOLTAGE_MULTI;
  return voltage;
}
uint8_t voltageGet(void)
{
  uint8_t voltage = 0;
  if(voltageGet_t() > 90)
    voltage = voltageGet_t() - 90;
  else
    voltage = 0;

  return voltage;
}

/**
 * @brief: get power
 * return :power unit:0.1w
 * */
static uint16_t powerGet_t(void)
{
  uint16_t power = 0;
  power = energyFre.power * calibration.powerCalibration * 10 / POWER_MULTI;
  return power;
}
uint16_t powerGet(void)
{
  uint16_t power = 0;
  power = getSlideWindowAvg(&powerWindow);
  return power;
}

/**
 * @brief: get summation energy
 * return :kwh  unit:0.1 kwh
 * */
uint32_t summaEnergyGet(void)   //
{
  uint32_t summation = 0;
  if(getThisParam(smartPlugEnergy, &summation) != 0) {
      summation = 0;
  }
  summation += (energyCnt * calibration.powerCalibration / 360000);

  return (summation / 100);
}

/**
 * @brief: calibration finish callback
 * */
__WEAK void calibration_finist_callback(void)
{

}

#endif
